package com.fingard.dsp.bank.directbank.vbao03.trade.utils;

import com.vbao.service.trade.utils.Base64ForRSA;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.*;
import java.util.Enumeration;

public abstract class RSAUtil {

    /**
     * 获取RSA公钥对象
     *
     * @param filePath     RSA公钥路径
     * @param fileSuffix   RSA公钥名称，决定编码类型
     * @param keyAlgorithm 密钥算法
     * @return RSA公钥对象
     * @throws Exception
     */
    public static PublicKey getRSAPublicKeyByFileSuffix(String filePath, String fileSuffix, String keyAlgorithm) throws Exception {
        InputStream in = null;
        String keyType = "";
        if ("crt".equalsIgnoreCase(fileSuffix) || "txt".equalsIgnoreCase(fileSuffix) || "cer".equalsIgnoreCase(fileSuffix)) {
            keyType = "X.509";
        } else if ("pem".equalsIgnoreCase(fileSuffix)) {
            keyType = "PKCS12";
        } else if (("yljf").equalsIgnoreCase(fileSuffix)) {
            keyType = "yljf";
        } else {
            keyType = "PKCS12";
        }

        try {
            in = new FileInputStream(filePath);
            PublicKey pubKey = null;
            if ("X.509".equals(keyType)) {
                CertificateFactory factory = CertificateFactory.getInstance(keyType);
                Certificate cert = factory.generateCertificate(in);
                pubKey = cert.getPublicKey();
            } else if ("PKCS12".equals(keyType)) {
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
                StringBuilder sb = new StringBuilder();
                String readLine = null;
                while ((readLine = br.readLine()) != null) {
                    if (readLine.charAt(0) == '-') {
                        continue;
                    } else {
                        sb.append(readLine);
                        sb.append('\r');
                    }
                }
                X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(Base64ForRSA.decode(sb.toString()));
                KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
                pubKey = keyFactory.generatePublic(pubX509);
            } else if ("yljf".equals(keyType)) {
                BufferedReader br = new BufferedReader(new InputStreamReader(in, "utf-8"));
                String s = br.readLine();
                ASN1InputStream ain = new ASN1InputStream(hexString2ByteArr(s));
                RSAPublicKeyStructure pStruct = RSAPublicKeyStructure.getInstance(ain.readObject());
                RSAPublicKeySpec spec = new RSAPublicKeySpec(pStruct.getModulus(), pStruct.getPublicExponent());
                KeyFactory kf = KeyFactory.getInstance("RSA");
                if (in != null)
                    in.close();
                return kf.generatePublic(spec);
            }

            return pubKey;
        } catch (FileNotFoundException e) {
            throw new Exception("公钥路径文件不存在");
        } catch (CertificateException e) {
            throw new Exception("生成证书文件错误");
        } catch (IOException e) {
            throw new Exception("读取公钥异常");
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(String.format("生成密钥工厂时没有[%s]此类算法", keyAlgorithm));
        } catch (InvalidKeySpecException e) {
            throw new Exception("生成公钥对象异常");
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
            }
        }
    }

    /**
     * 获取RSA私钥对象
     *
     * @param filePath     RSA私钥路径
     * @param fileSuffix   RSA私钥名称，决定编码类型
     * @param password     RSA私钥保护密钥
     * @param keyAlgorithm 密钥算法
     * @return RSA私钥对象
     * @throws Exception
     */
    @SuppressWarnings("deprecation")
    public static PrivateKey getRSAPrivateKeyByFileSuffix(String filePath, String fileSuffix, String password, String keyAlgorithm)
            throws Exception {
        String keyType = "";
        if ("keystore".equalsIgnoreCase(fileSuffix)) {
            keyType = "JKS";
        } else if ("pfx".equalsIgnoreCase(fileSuffix) || "p12".equalsIgnoreCase(fileSuffix)) {
            keyType = "PKCS12";
        } else if ("jck".equalsIgnoreCase(fileSuffix)) {
            keyType = "JCEKS";
        } else if ("pem".equalsIgnoreCase(fileSuffix) || "pkcs8".equalsIgnoreCase(fileSuffix)) {
            keyType = "PKCS8";
        } else if ("pkcs1".equalsIgnoreCase(fileSuffix)) {
            keyType = "PKCS1";
        } else if ("yljf".equalsIgnoreCase(fileSuffix)) {
            keyType = "yljf";
        } else if ("ldys".equalsIgnoreCase(fileSuffix)) {
            keyType = "ldys";
        } else {
            keyType = "JKS";
        }

        InputStream in = null;
        try {
            in = new FileInputStream(filePath);
            PrivateKey priKey = null;
            if ("JKS".equals(keyType) || "PKCS12".equals(keyType) || "JCEKS".equals(keyType)) {
                KeyStore ks = KeyStore.getInstance(keyType);
                if (password != null) {
                    char[] cPasswd = password.toCharArray();
                    ks.load(in, cPasswd);
                    Enumeration<String> aliasenum = ks.aliases();
                    String keyAlias = null;
                    while (aliasenum.hasMoreElements()) {
                        keyAlias = (String) aliasenum.nextElement();
                        priKey = (PrivateKey) ks.getKey(keyAlias, cPasswd);
                        if (priKey != null)
                            break;
                    }
                }
            } else if ("yljf".equals(keyType)) {
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
                String s = br.readLine();
                PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(hexStrToBytes(s));
                KeyFactory keyf = KeyFactory.getInstance("RSA");
                PrivateKey myprikey = keyf.generatePrivate(priPKCS8);
                return myprikey;
            } else if ("ldys".equals(keyType)) {
                byte[] b = new byte[20480];
                in.read(b);
                PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(b);
                KeyFactory keyf = KeyFactory.getInstance("RSA");
                PrivateKey myprikey = keyf.generatePrivate(priPKCS8);
                return myprikey;
            } else {
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
                StringBuilder sb = new StringBuilder();
                String readLine = null;
                while ((readLine = br.readLine()) != null) {
                    if (readLine.charAt(0) == '-') {
                        continue;
                    } else {
                        sb.append(readLine);
                        sb.append('\r');
                    }
                }
                if ("PKCS8".equals(keyType)) {
                    PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64ForRSA.decode(sb.toString()));
                    KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
                    priKey = keyFactory.generatePrivate(priPKCS8);
                } else if ("PKCS1".equals(keyType)) {
                    RSAPrivateKeyStructure asn1PrivKey = new RSAPrivateKeyStructure(
                            (ASN1Sequence) ASN1Sequence.fromByteArray(Base64ForRSA.decode(sb.toString())));
                    KeySpec rsaPrivKeySpec = new RSAPrivateKeySpec(
                            asn1PrivKey.getModulus(), asn1PrivKey.getPrivateExponent());
                    KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
                    priKey = keyFactory.generatePrivate(rsaPrivKeySpec);
                }
            }

            return priKey;
        } catch (FileNotFoundException e) {
            throw new Exception("私钥路径文件不存在");
        } catch (KeyStoreException e) {
            throw new Exception("获取KeyStore对象异常");
        } catch (IOException e) {
            throw new Exception("读取私钥异常");
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("生成私钥对象异常");
        } catch (CertificateException e) {
            throw new Exception("加载私钥密码异常");
        } catch (UnrecoverableKeyException e) {
            throw new Exception("生成私钥对象异常");
        } catch (InvalidKeySpecException e) {
            throw new Exception("生成私钥对象异常");
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
            }
        }
    }

    /**
     * RSA加密
     *
     * @param plainBytes      明文字节数组
     * @param publicKey       公钥
     * @param keyLength       密钥bit长度
     * @param reserveSize     padding填充字节数，预留11字节
     * @param cipherAlgorithm 加解密算法，一般为RSA/ECB/PKCS1Padding
     * @return 加密后字节数组，不经base64编码
     * @throws Exception
     */
    public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm)
            throws Exception {
        int keyByteSize = keyLength / 8; // 密钥字节数
        int encryptBlockSize = keyByteSize - reserveSize; // 加密块大小=密钥字节数-padding填充字节数
        int nBlock = plainBytes.length / encryptBlockSize;// 计算分段加密的block数，向上取整
        if ((plainBytes.length % encryptBlockSize) != 0) { // 余数非0，block数再加1
            nBlock += 1;
        }

        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            // 输出buffer，大小为nBlock个keyByteSize
            ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
            // 分段加密
            for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
                int inputLen = plainBytes.length - offset;
                if (inputLen > encryptBlockSize) {
                    inputLen = encryptBlockSize;
                }

                // 得到分段加密结果
                byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
                // 追加结果到输出buffer中
                outbuf.write(encryptedBlock);
            }

            outbuf.flush();
            outbuf.close();
            return outbuf.toByteArray();
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
        } catch (NoSuchPaddingException e) {
            throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("无效密钥");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("加密块大小不合法");
        } catch (BadPaddingException e) {
            throw new Exception("错误填充模式");
        } catch (IOException e) {
            throw new Exception("字节输出流异常");
        }
    }

    /**
     * RSA解密
     *
     * @param encryptedBytes  加密后字节数组
     * @param privateKey      私钥
     * @param keyLength       密钥bit长度
     * @param reserveSize     padding填充字节数，预留11字节
     * @param cipherAlgorithm 加解密算法，一般为RSA/ECB/PKCS1Padding
     * @return 解密后字节数组，不经base64编码
     * @throws Exception
     */
    public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm)
            throws Exception {
        int keyByteSize = keyLength / 8; // 密钥字节数
        int decryptBlockSize = keyByteSize - reserveSize; // 解密块大小=密钥字节数-padding填充字节数
        int nBlock = encryptedBytes.length / keyByteSize;// 计算分段解密的block数，理论上能整除

        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            // 输出buffer，大小为nBlock个decryptBlockSize
            ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
            // 分段解密
            for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
                // block大小: decryptBlock 或 剩余字节数
                int inputLen = encryptedBytes.length - offset;
                if (inputLen > keyByteSize) {
                    inputLen = keyByteSize;
                }

                // 得到分段解密结果
                byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
                // 追加结果到输出buffer中
                outbuf.write(decryptedBlock);
            }

            outbuf.flush();
            outbuf.close();
            return outbuf.toByteArray();
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(String.format("没有[%s]此类解密算法", cipherAlgorithm));
        } catch (NoSuchPaddingException e) {
            throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("无效密钥");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("解密块大小不合法");
        } catch (BadPaddingException e) {
            throw new Exception("错误填充模式");
        } catch (IOException e) {
            throw new Exception("字节输出流异常");
        }
    }

    public static byte[] hexString2ByteArr(String hexStr) {
        return new BigInteger(hexStr, 16).toByteArray();
    }

    public static final byte[] hexStrToBytes(String s) {
        byte[] bytes;
        bytes = new byte[s.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16);
        }
        return bytes;
    }


}
